library("igraph")
library("plotly")
edges <- read.csv( "emails.edges" , header = TRUE, ,sep ='')
edges_with_relation <- edges
edges_with_relation$fromGestor <- edges_with_relation$from < 144
edges_with_relation$toGestor <- edges_with_relation$to < 144
g <- graph.data.frame( edges , directed = TRUE)
g <- simplify(g, edge.attr.comb = "sum")
d <- degree(as.undirected(g)) #Grados de los nodos
max(d) #Grado máximo
min(d) #Grado mínimo
d[which.max(degree(as.undirected(g)))] #Nodo que tiene el grado máximo
# Creación de una tabla con los ID's
nodes_df <- as.data.frame(d)
nodes_df <- cbind(id = rownames(nodes_df), nodes_df)
rownames(nodes_df) <- 1:nrow(nodes_df)
nodes_df$id <- as.numeric(nodes_df$id)
cat("\n Número de empleados\n",length(unique(nodes_df[["id"]])))

 Número de empleados
 87273
nodes_df$d_in <- as.data.frame(degree(g, mode="in"))$d
nodes_df$d_out <- as.data.frame(degree(g, mode="out"))$d
nodes_df$isGestor <- ifelse(nodes_df$id < 144, 1, 0)

nodes_df <- nodes_df %>%
  arrange(nodes_df$id)
fig <- plot_ly(x=nodes_df$d, type = "box", name = "") 

fig <- fig %>% layout(yaxis=list(type='linear'))

fig <- fig %>%
        layout(title = 'Distribución de los grados de los nodos', plot_bgcolor = "#e5ecf6")

fig <- fig %>% layout(
  barmode="group",
  bargap=0.2)

fig
nodes_manager_df <- subset(nodes_df, isGestor == 1)
nodes_regular_employee_df <- subset(nodes_df, isGestor == 0)
fig <- plot_ly(x=nodes_manager_df$d, histfunc='sum', type = "histogram", name = "Managers", nbinsx=25) 

fig <- fig %>% layout(yaxis=list(type='linear'))

fig <- fig %>%
        layout(title = 'Distribución de los grados de managers', plot_bgcolor = "#e5ecf6")

fig <- fig %>% layout(
  barmode="group",
  bargap=0.2)

fig
fig <- plot_ly(x=nodes_regular_employee_df$d, histfunc='sum', type = "histogram", name = "Regular employees", nbinsx=25) 

fig <- fig %>% layout(yaxis=list(type='linear'))

fig <- fig %>%
        layout(title = 'Distribución de los grados de empleados regulares', plot_bgcolor = "#e5ecf6")

fig <- fig %>% layout(
  barmode="group",
  bargap=0.2)

fig
cat("Grado medio de los nodos managers: \t", mean(nodes_manager_df$d))
cat("\nGrado medio de los nodos regulares: \t", mean(nodes_regular_employee_df$d))

cat("\nGrado máximo de los nodos managers: \t", max(nodes_manager_df$d))
cat("\nGrado máximo de los nodos regulares: \t", max(nodes_regular_employee_df$d))

degree
top_degrees <- nodes_df %>%
  arrange(desc(d)) %>% head(10)
fig <- plot_ly(
  x = as.factor(top_degrees$id),
  y = top_degrees$d,
  name = "Top 10 nodos con mayor grado",
  text = top_degrees$d, # add the text argument
  textposition = "inside", # display the text inside the bars
  type = "bar"
)
fig

Distribución de grados entrantes y salientes

top_degrees_in <- nodes_df %>%
  arrange(desc(d_in)) %>% head(10)

fig <- plot_ly(
  x = as.factor(top_degrees_in$id),
  y = top_degrees_in$d_in,
  name = "Top 10 nodos con mayor grado entrante",
  text = top_degrees_in$d_in, # add the text argument
  textposition = "inside", # display the text inside the bars
  type = "bar"
)
fig
top_degrees_in <- nodes_df %>%
  arrange(desc(d_out)) %>% head(10)

fig <- plot_ly(
  x = as.factor(top_degrees_in$id),
  y = top_degrees_in$d_out,
  name = "Top 10 nodos con mayor grado saliente",
  text = top_degrees_in$d_out, # add the text argument
  textposition = "inside", # display the text inside the bars
  type = "bar"
)
fig
fig <- plot_ly(x=nodes_manager_df$d_in, histfunc='sum', type = "histogram", name = "Grados entrantes", nbinsx=25)  %>%add_trace(x=nodes_manager_df$d_out, histfunc='sum', type = "histogram", name = "Grados salientes", nbinsx=25) 

fig <- fig %>% layout(yaxis=list(type='linear'))

fig <- fig %>%
        layout(title = 'Distribución de los grados entrantes/salientes de gestores', plot_bgcolor = "#e5ecf6")

fig <- fig %>% layout(
  barmode="group",
  bargap=0.2)

fig
fig <- plot_ly(x=nodes_regular_employee_df$d_in, histfunc='sum', type = "histogram", name = "Grados entrantes", nbinsx=25)  %>%add_trace(x=nodes_regular_employee_df$d_out, histfunc='sum', type = "histogram", name = "Grados salientes", nbinsx=25) 

fig <- fig %>% layout(yaxis=list(type='linear'))

fig <- fig %>%
        layout(title = 'Distribución de los grados entrantes/salientes de empleados regulares', plot_bgcolor = "#e5ecf6")

fig <- fig %>% layout(
  barmode="group",
  bargap=0.2)

fig

Simplificación para medidas estructurales

Dado que no es computacionalmente factible calcular las medidas estructurales con el grafo de emails.edges original, hemos decidido simplificar el problema planteando un subgrafo colapsado para un periodo de tiempo más reducido a partir del fichero emails-timestamps.edges, en este caso se ha considerado el año 2001.

Medidas estructurales sobre el grafo reducido

edges <- read.csv( "emails-reduced-december-2000.edges" , header = TRUE, ,sep ='')
edges_with_relation <- edges
edges_with_relation$weight <-NULL
edges_with_relation$fromGestor <- edges_with_relation$from < 144
edges_with_relation$toGestor <- edges_with_relation$to < 144
g <- graph.data.frame( edges , directed = TRUE)
g <- simplify(g, edge.attr.comb = "sum")
Error in simplify(g, edge.attr.comb = "sum") : 
  unused argument (edge.attr.comb = "sum")
diameter(g, directed=TRUE, weight=NA)
diameter(g, directed=FALSE, weight=NA)
#distances(as.undirected(g), weight=NA) # --> Demasiado grande!
mean_distance(as.undirected(g))
#all_shortest_paths(as.undirected(g), from=2, weights=NA) # --> Demasiado grande!
#Componentes del grafo
is.connected(g, mode="weak")
[1] FALSE
is.connected(g, mode="strong")
[1] FALSE
componentes <- components(g, mode="strong")

length(componentes$csize)
[1] 78058
cd <-component_distribution(g)

cmax <- induced.subgraph(g, which(clusters(g)$membership == which.max(clusters(g)$csize)))
articulation.points(g)
+ 4152/87273 vertices, named, from 2927be6:
   [1] 9186  13547 11710 11453 10800 1356  2909  19310 10584 20769 26948 1564  17964 18351 22536 21674 1296  11919 403  
  [20] 12452 12337 268   267   17865 960   11722 12591 5227  13552 13394 14017 11909 11910 14604 3082  3023  27161 27162
  [39] 52879 27971 3158  13545 8427  566   19978 3081  19206 17984 31362 66659 574   494   33039 37622 4254  36580 8362 
  [58] 19073 17222 1420  1454  5758  13802 11807 17427 17376 28250 66707 1012  2356  39524 39543 59698 80041 2301  20344
  [77] 41230 80926 19802 18715 18705 42727 6192  36456 30437 85745 33723 3702  71807 12051 28492 5573  35184 472   47929
  [96] 48015 49221 48952 48675 47996 7544  49025 48295 48484 48422 48508 48505 21469 26675 23751 21660 25122 21153 21052
 [115] 21176 21012 20846 21863 22199 26995 21933 21476 21448 21166 22796 21787 27005 27000 21116 24037 64327 64317 45662
 [134] 31666 6720  51266 46235 30122 30302 30216 30206 45701 37476 41631 41719 41615 41624 9914  40025 877   14491 38898
 [153] 7654  38706 38694 7630  7655  7615  7638  7632  30756 29313 28398 28703 46628 10214 63777 77597 77674 77621 5492 
 [172] 3528  59131 4363  74732 48833 73489 74838 33738 57548 57508 57512 57709 60527 80234 80241 45675 8293  80389 82086
+ ... omitted several vertices
max(componentes$csize)
[1] 9164
V(g)$isGestor <- nodes_df$isGestor
#Medidas estructurales
graph.density(as.undirected(g))
transitivity(g)
reciprocity(g)
assortativity.degree(g)
assortativity_nominal(g,as.factor(V(g)$isGestor))
clique.number(g)
cliques(g)

Medidas de centralidad

b <- betweenness(g)
c <- closeness(g)
e <- eigen_centrality(g)$vector
pr <- page_rank(g,directed=FALSE,weights=NA)$vector
nodes_df$b <-b
nodes_df$c <-c
nodes_df$e <-e
nodes_df$pr <-pr
top_betweenness <- nodes_df %>%
  arrange(desc(b)) %>% head(5)

fig <- plot_ly(
  x = as.factor(top_betweenness$id),
  y = top_betweenness$b,
  name = "Top 10 nodos con mayor grado",
  text = top_betweenness$b, # add the text argument
  textposition = "inside", # display the text inside the bars
  type = "bar"
)
fig <- fig %>%
        layout(title = 'Top 5 centralidades de intermediario', plot_bgcolor = "#e5ecf6")

fig
top_closenness <- nodes_df %>%
  arrange(desc(c)) %>% head(5)

fig <- plot_ly(
  x = as.factor(top_closenness$id),
  y = top_closenness$c,
  name = "Top 10 nodos con mayor grado",
  text = top_closenness$c, # add the text argument
  textposition = "inside", # display the text inside the bars
  type = "bar"
)
fig <- fig %>%
        layout(title = 'Top 5 centralidades de cercanía', plot_bgcolor = "#e5ecf6")

fig
top_eigenvector <- nodes_df %>%
  arrange(desc(e)) %>% head(5)

fig <- plot_ly(
  x = as.factor(top_eigenvector$id),
  y = top_eigenvector$e,
  name = "Top 10 nodos con mayor grado",
  text = top_eigenvector$e, # add the text argument
  textposition = "inside", # display the text inside the bars
  type = "bar"
)
fig <- fig %>%
        layout(title = 'Top 5 centralidades de autovector', plot_bgcolor = "#e5ecf6")

fig
top_pagerank<- nodes_df %>%
  arrange(desc(pr)) %>% head(5)

fig <- plot_ly(
  x = as.factor(top_pagerank$id),
  y = top_pagerank$pr,
  name = "Top 10 nodos con mayor grado",
  text = top_pagerank$pr, # add the text argument
  textposition = "inside", # display the text inside the bars
  type = "bar"
)
fig <- fig %>%
        layout(title = 'Top 5 centralidades de PageRank', plot_bgcolor = "#e5ecf6")

fig

Comunidades

#Comunidades - Girvan-Newman
ebc <- edge.betweenness.community(as.undirected(g))
Warning: At core/community/edge_betweenness.c:493 : Membership vector will be selected based on the highest modularity score.Warning: At core/community/edge_betweenness.c:500 : Modularity calculation with weighted edge betweenness community detection might not make sense -- modularity treats edge weights as similarities while edge betwenness treats them as distances.
NA
plot(ebc, as.undirected(g))
plot_dendrogram(ebc)
#Comunidades - Particiones espectrales
lec <- leading.eigenvector.community(as.undirected(g))
plot(lec, as.undirected(g))
#Comunidades - Louvain
plot(cluster_louvain(as.undirected(g)),as.undirected(g))
#Comunidades - Leiden
plot(cluster_leiden(as.undirected(g),objective_function = "modularity"),as.undirected(g))
LS0tCnRpdGxlOiAiRW1haWxzIGFuYWx5c2lzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7cn0KbGlicmFyeSgiaWdyYXBoIikKbGlicmFyeSgicGxvdGx5IikKYGBgCgpgYGB7cn0KZWRnZXMgPC0gcmVhZC5jc3YoICJlbWFpbHMuZWRnZXMiICwgaGVhZGVyID0gVFJVRSwgLHNlcCA9JycpCmVkZ2VzX3dpdGhfcmVsYXRpb24gPC0gZWRnZXMKZWRnZXNfd2l0aF9yZWxhdGlvbiRmcm9tR2VzdG9yIDwtIGVkZ2VzX3dpdGhfcmVsYXRpb24kZnJvbSA8IDE0NAplZGdlc193aXRoX3JlbGF0aW9uJHRvR2VzdG9yIDwtIGVkZ2VzX3dpdGhfcmVsYXRpb24kdG8gPCAxNDQKZyA8LSBncmFwaC5kYXRhLmZyYW1lKCBlZGdlcyAsIGRpcmVjdGVkID0gVFJVRSkKZyA8LSBzaW1wbGlmeShnLCBlZGdlLmF0dHIuY29tYiA9ICJzdW0iKQpkIDwtIGRlZ3JlZShhcy51bmRpcmVjdGVkKGcpKSAjR3JhZG9zIGRlIGxvcyBub2RvcwptYXgoZCkgI0dyYWRvIG3DoXhpbW8KbWluKGQpICNHcmFkbyBtw61uaW1vCmRbd2hpY2gubWF4KGRlZ3JlZShhcy51bmRpcmVjdGVkKGcpKSldICNOb2RvIHF1ZSB0aWVuZSBlbCBncmFkbyBtw6F4aW1vCmBgYAoKYGBge3J9CiMgQ3JlYWNpw7NuIGRlIHVuYSB0YWJsYSBjb24gbG9zIElEJ3MKbm9kZXNfZGYgPC0gYXMuZGF0YS5mcmFtZShkKQpub2Rlc19kZiA8LSBjYmluZChpZCA9IHJvd25hbWVzKG5vZGVzX2RmKSwgbm9kZXNfZGYpCnJvd25hbWVzKG5vZGVzX2RmKSA8LSAxOm5yb3cobm9kZXNfZGYpCm5vZGVzX2RmJGlkIDwtIGFzLm51bWVyaWMobm9kZXNfZGYkaWQpCmNhdCgiXG4gTsO6bWVybyBkZSBlbXBsZWFkb3NcbiIsbGVuZ3RoKHVuaXF1ZShub2Rlc19kZltbImlkIl1dKSkpCm5vZGVzX2RmJGRfaW4gPC0gYXMuZGF0YS5mcmFtZShkZWdyZWUoZywgbW9kZT0iaW4iKSkkZApub2Rlc19kZiRkX291dCA8LSBhcy5kYXRhLmZyYW1lKGRlZ3JlZShnLCBtb2RlPSJvdXQiKSkkZApub2Rlc19kZiRpc0dlc3RvciA8LSBpZmVsc2Uobm9kZXNfZGYkaWQgPCAxNDQsIDEsIDApCgpub2Rlc19kZiA8LSBub2Rlc19kZiAlPiUKICBhcnJhbmdlKG5vZGVzX2RmJGlkKQoKYGBgCgpgYGB7cn0KZmlnIDwtIHBsb3RfbHkoeD1ub2Rlc19kZiRkLCB0eXBlID0gImJveCIsIG5hbWUgPSAiIikgCgpmaWcgPC0gZmlnICU+JSBsYXlvdXQoeWF4aXM9bGlzdCh0eXBlPSdsaW5lYXInKSkKCmZpZyA8LSBmaWcgJT4lCiAgICAgICAgbGF5b3V0KHRpdGxlID0gJ0Rpc3RyaWJ1Y2nDs24gZGUgbG9zIGdyYWRvcyBkZSBsb3Mgbm9kb3MnLCBwbG90X2JnY29sb3IgPSAiI2U1ZWNmNiIpCgpmaWcgPC0gZmlnICU+JSBsYXlvdXQoCiAgYmFybW9kZT0iZ3JvdXAiLAogIGJhcmdhcD0wLjIpCgpmaWcKYGBgCgoKYGBge3J9Cm5vZGVzX21hbmFnZXJfZGYgPC0gc3Vic2V0KG5vZGVzX2RmLCBpc0dlc3RvciA9PSAxKQpub2Rlc19yZWd1bGFyX2VtcGxveWVlX2RmIDwtIHN1YnNldChub2Rlc19kZiwgaXNHZXN0b3IgPT0gMCkKYGBgCgpgYGB7cn0KZmlnIDwtIHBsb3RfbHkoeD1ub2Rlc19tYW5hZ2VyX2RmJGQsIGhpc3RmdW5jPSdzdW0nLCB0eXBlID0gImhpc3RvZ3JhbSIsIG5hbWUgPSAiTWFuYWdlcnMiLCBuYmluc3g9MjUpIAoKZmlnIDwtIGZpZyAlPiUgbGF5b3V0KHlheGlzPWxpc3QodHlwZT0nbGluZWFyJykpCgpmaWcgPC0gZmlnICU+JQogICAgICAgIGxheW91dCh0aXRsZSA9ICdEaXN0cmlidWNpw7NuIGRlIGxvcyBncmFkb3MgZGUgbWFuYWdlcnMnLCBwbG90X2JnY29sb3IgPSAiI2U1ZWNmNiIpCgpmaWcgPC0gZmlnICU+JSBsYXlvdXQoCiAgYmFybW9kZT0iZ3JvdXAiLAogIGJhcmdhcD0wLjIpCgpmaWcKYGBgCgpgYGB7cn0KZmlnIDwtIHBsb3RfbHkoeD1ub2Rlc19yZWd1bGFyX2VtcGxveWVlX2RmJGQsIGhpc3RmdW5jPSdzdW0nLCB0eXBlID0gImhpc3RvZ3JhbSIsIG5hbWUgPSAiUmVndWxhciBlbXBsb3llZXMiLCBuYmluc3g9MjUpIAoKZmlnIDwtIGZpZyAlPiUgbGF5b3V0KHlheGlzPWxpc3QodHlwZT0nbGluZWFyJykpCgpmaWcgPC0gZmlnICU+JQogICAgICAgIGxheW91dCh0aXRsZSA9ICdEaXN0cmlidWNpw7NuIGRlIGxvcyBncmFkb3MgZGUgZW1wbGVhZG9zIHJlZ3VsYXJlcycsIHBsb3RfYmdjb2xvciA9ICIjZTVlY2Y2IikKCmZpZyA8LSBmaWcgJT4lIGxheW91dCgKICBiYXJtb2RlPSJncm91cCIsCiAgYmFyZ2FwPTAuMikKCmZpZwpgYGAKCmBgYHtyfQpjYXQoIkdyYWRvIG1lZGlvIGRlIGxvcyBub2RvcyBtYW5hZ2VyczogXHQiLCBtZWFuKG5vZGVzX21hbmFnZXJfZGYkZCkpCmNhdCgiXG5HcmFkbyBtZWRpbyBkZSBsb3Mgbm9kb3MgcmVndWxhcmVzOiBcdCIsIG1lYW4obm9kZXNfcmVndWxhcl9lbXBsb3llZV9kZiRkKSkKCmNhdCgiXG5HcmFkbyBtw6F4aW1vIGRlIGxvcyBub2RvcyBtYW5hZ2VyczogXHQiLCBtYXgobm9kZXNfbWFuYWdlcl9kZiRkKSkKY2F0KCJcbkdyYWRvIG3DoXhpbW8gZGUgbG9zIG5vZG9zIHJlZ3VsYXJlczogXHQiLCBtYXgobm9kZXNfcmVndWxhcl9lbXBsb3llZV9kZiRkKSkKCmRlZ3JlZQoKYGBgCgpgYGB7cn0KdG9wX2RlZ3JlZXMgPC0gbm9kZXNfZGYgJT4lCiAgYXJyYW5nZShkZXNjKGQpKSAlPiUgaGVhZCgxMCkKYGBgCgpgYGB7cn0KZmlnIDwtIHBsb3RfbHkoCiAgeCA9IGFzLmZhY3Rvcih0b3BfZGVncmVlcyRpZCksCiAgeSA9IHRvcF9kZWdyZWVzJGQsCiAgbmFtZSA9ICJUb3AgMTAgbm9kb3MgY29uIG1heW9yIGdyYWRvIiwKICB0ZXh0ID0gdG9wX2RlZ3JlZXMkZCwgIyBhZGQgdGhlIHRleHQgYXJndW1lbnQKICB0ZXh0cG9zaXRpb24gPSAiaW5zaWRlIiwgIyBkaXNwbGF5IHRoZSB0ZXh0IGluc2lkZSB0aGUgYmFycwogIHR5cGUgPSAiYmFyIgopCmZpZwpgYGAKCgoKCiMjIyBEaXN0cmlidWNpw7NuIGRlIGdyYWRvcyBlbnRyYW50ZXMgeSBzYWxpZW50ZXMKCgpgYGB7cn0KdG9wX2RlZ3JlZXNfaW4gPC0gbm9kZXNfZGYgJT4lCiAgYXJyYW5nZShkZXNjKGRfaW4pKSAlPiUgaGVhZCgxMCkKCmZpZyA8LSBwbG90X2x5KAogIHggPSBhcy5mYWN0b3IodG9wX2RlZ3JlZXNfaW4kaWQpLAogIHkgPSB0b3BfZGVncmVlc19pbiRkX2luLAogIG5hbWUgPSAiVG9wIDEwIG5vZG9zIGNvbiBtYXlvciBncmFkbyBlbnRyYW50ZSIsCiAgdGV4dCA9IHRvcF9kZWdyZWVzX2luJGRfaW4sICMgYWRkIHRoZSB0ZXh0IGFyZ3VtZW50CiAgdGV4dHBvc2l0aW9uID0gImluc2lkZSIsICMgZGlzcGxheSB0aGUgdGV4dCBpbnNpZGUgdGhlIGJhcnMKICB0eXBlID0gImJhciIKKQpmaWcKYGBgCgpgYGB7cn0KdG9wX2RlZ3JlZXNfaW4gPC0gbm9kZXNfZGYgJT4lCiAgYXJyYW5nZShkZXNjKGRfb3V0KSkgJT4lIGhlYWQoMTApCgpmaWcgPC0gcGxvdF9seSgKICB4ID0gYXMuZmFjdG9yKHRvcF9kZWdyZWVzX2luJGlkKSwKICB5ID0gdG9wX2RlZ3JlZXNfaW4kZF9vdXQsCiAgbmFtZSA9ICJUb3AgMTAgbm9kb3MgY29uIG1heW9yIGdyYWRvIHNhbGllbnRlIiwKICB0ZXh0ID0gdG9wX2RlZ3JlZXNfaW4kZF9vdXQsICMgYWRkIHRoZSB0ZXh0IGFyZ3VtZW50CiAgdGV4dHBvc2l0aW9uID0gImluc2lkZSIsICMgZGlzcGxheSB0aGUgdGV4dCBpbnNpZGUgdGhlIGJhcnMKICB0eXBlID0gImJhciIKKQpmaWcKCmBgYAoKYGBge3J9CmZpZyA8LSBwbG90X2x5KHg9bm9kZXNfbWFuYWdlcl9kZiRkX2luLCBoaXN0ZnVuYz0nc3VtJywgdHlwZSA9ICJoaXN0b2dyYW0iLCBuYW1lID0gIkdyYWRvcyBlbnRyYW50ZXMiLCBuYmluc3g9MjUpICAlPiVhZGRfdHJhY2UoeD1ub2Rlc19tYW5hZ2VyX2RmJGRfb3V0LCBoaXN0ZnVuYz0nc3VtJywgdHlwZSA9ICJoaXN0b2dyYW0iLCBuYW1lID0gIkdyYWRvcyBzYWxpZW50ZXMiLCBuYmluc3g9MjUpIAoKZmlnIDwtIGZpZyAlPiUgbGF5b3V0KHlheGlzPWxpc3QodHlwZT0nbGluZWFyJykpCgpmaWcgPC0gZmlnICU+JQogICAgICAgIGxheW91dCh0aXRsZSA9ICdEaXN0cmlidWNpw7NuIGRlIGxvcyBncmFkb3MgZW50cmFudGVzL3NhbGllbnRlcyBkZSBnZXN0b3JlcycsIHBsb3RfYmdjb2xvciA9ICIjZTVlY2Y2IikKCmZpZyA8LSBmaWcgJT4lIGxheW91dCgKICBiYXJtb2RlPSJncm91cCIsCiAgYmFyZ2FwPTAuMikKCmZpZwpgYGAKCgpgYGB7cn0KZmlnIDwtIHBsb3RfbHkoeD1ub2Rlc19yZWd1bGFyX2VtcGxveWVlX2RmJGRfaW4sIGhpc3RmdW5jPSdzdW0nLCB0eXBlID0gImhpc3RvZ3JhbSIsIG5hbWUgPSAiR3JhZG9zIGVudHJhbnRlcyIsIG5iaW5zeD0yNSkgICU+JWFkZF90cmFjZSh4PW5vZGVzX3JlZ3VsYXJfZW1wbG95ZWVfZGYkZF9vdXQsIGhpc3RmdW5jPSdzdW0nLCB0eXBlID0gImhpc3RvZ3JhbSIsIG5hbWUgPSAiR3JhZG9zIHNhbGllbnRlcyIsIG5iaW5zeD0yNSkgCgpmaWcgPC0gZmlnICU+JSBsYXlvdXQoeWF4aXM9bGlzdCh0eXBlPSdsaW5lYXInKSkKCmZpZyA8LSBmaWcgJT4lCiAgICAgICAgbGF5b3V0KHRpdGxlID0gJ0Rpc3RyaWJ1Y2nDs24gZGUgbG9zIGdyYWRvcyBlbnRyYW50ZXMvc2FsaWVudGVzIGRlIGVtcGxlYWRvcyByZWd1bGFyZXMnLCBwbG90X2JnY29sb3IgPSAiI2U1ZWNmNiIpCgpmaWcgPC0gZmlnICU+JSBsYXlvdXQoCiAgYmFybW9kZT0iZ3JvdXAiLAogIGJhcmdhcD0wLjIpCgpmaWcKYGBgCgojIyMgU2ltcGxpZmljYWNpw7NuIHBhcmEgbWVkaWRhcyBlc3RydWN0dXJhbGVzCgpEYWRvIHF1ZSBubyBlcyBjb21wdXRhY2lvbmFsbWVudGUgZmFjdGlibGUgY2FsY3VsYXIgbGFzIG1lZGlkYXMgZXN0cnVjdHVyYWxlcyBjb24gZWwgZ3JhZm8gZGUgZW1haWxzLmVkZ2VzIG9yaWdpbmFsLCBoZW1vcyBkZWNpZGlkbyBzaW1wbGlmaWNhciBlbCBwcm9ibGVtYSBwbGFudGVhbmRvIHVuIHN1YmdyYWZvIGNvbGFwc2FkbyBwYXJhIHVuIHBlcmlvZG8gZGUgdGllbXBvIG3DoXMgcmVkdWNpZG8gYSBwYXJ0aXIgZGVsIGZpY2hlcm8gZW1haWxzLXRpbWVzdGFtcHMuZWRnZXMsIGVuIGVzdGUgY2FzbyBzZSBoYSBjb25zaWRlcmFkbyBlbCBhw7FvIDIwMDEuCgoKIyMjIE1lZGlkYXMgZXN0cnVjdHVyYWxlcyBzb2JyZSBlbCBncmFmbyByZWR1Y2lkbwoKCmBgYHtyfQplZGdlcyA8LSByZWFkLmNzdiggImVtYWlscy1yZWR1Y2VkLWRlY2VtYmVyLTIwMDAuZWRnZXMiICwgaGVhZGVyID0gVFJVRSwgLHNlcCA9JycpCmVkZ2VzX3dpdGhfcmVsYXRpb24gPC0gZWRnZXMKZWRnZXNfd2l0aF9yZWxhdGlvbiR3ZWlnaHQgPC1OVUxMCmVkZ2VzX3dpdGhfcmVsYXRpb24kZnJvbUdlc3RvciA8LSBlZGdlc193aXRoX3JlbGF0aW9uJGZyb20gPCAxNDQKZWRnZXNfd2l0aF9yZWxhdGlvbiR0b0dlc3RvciA8LSBlZGdlc193aXRoX3JlbGF0aW9uJHRvIDwgMTQ0CmcgPC0gZ3JhcGguZGF0YS5mcmFtZSggZWRnZXMgLCBkaXJlY3RlZCA9IFRSVUUpCmcgPC0gc2ltcGxpZnkoZywgZWRnZS5hdHRyLmNvbWIgPSAic3VtIikKZCA8LSBkZWdyZWUoYXMudW5kaXJlY3RlZChnKSkgI0dyYWRvcyBkZSBsb3Mgbm9kb3MKbWF4KGQpICNHcmFkbyBtw6F4aW1vCm1pbihkKSAjR3JhZG8gbcOtbmltbwpkW3doaWNoLm1heChkZWdyZWUoYXMudW5kaXJlY3RlZChnKSkpXSAjTm9kbyBxdWUgdGllbmUgZWwgZ3JhZG8gbcOheGltbwpgYGAKCgoKYGBge3J9CmRpYW1ldGVyKGcsIGRpcmVjdGVkPVRSVUUsIHdlaWdodD1OQSkKZGlhbWV0ZXIoZywgZGlyZWN0ZWQ9RkFMU0UsIHdlaWdodD1OQSkKI2Rpc3RhbmNlcyhhcy51bmRpcmVjdGVkKGcpLCB3ZWlnaHQ9TkEpICMgLS0+IERlbWFzaWFkbyBncmFuZGUhCm1lYW5fZGlzdGFuY2UoYXMudW5kaXJlY3RlZChnKSkKI2FsbF9zaG9ydGVzdF9wYXRocyhhcy51bmRpcmVjdGVkKGcpLCBmcm9tPTIsIHdlaWdodHM9TkEpICMgLS0+IERlbWFzaWFkbyBncmFuZGUhCmBgYAoKYGBge3J9CiNDb21wb25lbnRlcyBkZWwgZ3JhZm8KaXMuY29ubmVjdGVkKGcsIG1vZGU9IndlYWsiKQppcy5jb25uZWN0ZWQoZywgbW9kZT0ic3Ryb25nIikKYGBgCgpgYGB7cn0KY29tcG9uZW50ZXMgPC0gY29tcG9uZW50cyhnLCBtb2RlPSJzdHJvbmciKQoKbGVuZ3RoKGNvbXBvbmVudGVzJGNzaXplKQoKY2QgPC1jb21wb25lbnRfZGlzdHJpYnV0aW9uKGcpCgpjbWF4IDwtIGluZHVjZWQuc3ViZ3JhcGgoZywgd2hpY2goY2x1c3RlcnMoZykkbWVtYmVyc2hpcCA9PSB3aGljaC5tYXgoY2x1c3RlcnMoZykkY3NpemUpKSkKYGBgCmBgYHtyfQphcnRpY3VsYXRpb24ucG9pbnRzKGcpCmBgYApgYGB7cn0KbWF4KGNvbXBvbmVudGVzJGNzaXplKQpgYGAKCmBgYHtyfQpWKGcpJGlzR2VzdG9yIDwtIG5vZGVzX2RmJGlzR2VzdG9yCmBgYAoKCmBgYHtyfQojTWVkaWRhcyBlc3RydWN0dXJhbGVzCmdyYXBoLmRlbnNpdHkoYXMudW5kaXJlY3RlZChnKSkKdHJhbnNpdGl2aXR5KGcpCnJlY2lwcm9jaXR5KGcpCmFzc29ydGF0aXZpdHkuZGVncmVlKGcpCmFzc29ydGF0aXZpdHlfbm9taW5hbChnLGFzLmZhY3RvcihWKGcpJGlzR2VzdG9yKSkKYGBgCgpgYGB7cn0KY2xpcXVlLm51bWJlcihnKQpjbGlxdWVzKGcpCmBgYAoKIyMjIE1lZGlkYXMgZGUgY2VudHJhbGlkYWQKCmBgYHtyfQpiIDwtIGJldHdlZW5uZXNzKGcpCmBgYAoKYGBge3J9CmMgPC0gY2xvc2VuZXNzKGcpCmBgYAoKYGBge3J9CmUgPC0gZWlnZW5fY2VudHJhbGl0eShnKSR2ZWN0b3IKYGBgCgpgYGB7cn0KcHIgPC0gcGFnZV9yYW5rKGcsZGlyZWN0ZWQ9RkFMU0Usd2VpZ2h0cz1OQSkkdmVjdG9yCmBgYAoKYGBge3J9Cm5vZGVzX2RmJGIgPC1iCm5vZGVzX2RmJGMgPC1jCm5vZGVzX2RmJGUgPC1lCm5vZGVzX2RmJHByIDwtcHIKYGBgCgpgYGB7cn0KdG9wX2JldHdlZW5uZXNzIDwtIG5vZGVzX2RmICU+JQogIGFycmFuZ2UoZGVzYyhiKSkgJT4lIGhlYWQoNSkKCmZpZyA8LSBwbG90X2x5KAogIHggPSBhcy5mYWN0b3IodG9wX2JldHdlZW5uZXNzJGlkKSwKICB5ID0gdG9wX2JldHdlZW5uZXNzJGIsCiAgbmFtZSA9ICJUb3AgMTAgbm9kb3MgY29uIG1heW9yIGdyYWRvIiwKICB0ZXh0ID0gdG9wX2JldHdlZW5uZXNzJGIsICMgYWRkIHRoZSB0ZXh0IGFyZ3VtZW50CiAgdGV4dHBvc2l0aW9uID0gImluc2lkZSIsICMgZGlzcGxheSB0aGUgdGV4dCBpbnNpZGUgdGhlIGJhcnMKICB0eXBlID0gImJhciIKKQpmaWcgPC0gZmlnICU+JQogICAgICAgIGxheW91dCh0aXRsZSA9ICdUb3AgNSBjZW50cmFsaWRhZGVzIGRlIGludGVybWVkaWFyaW8nLCBwbG90X2JnY29sb3IgPSAiI2U1ZWNmNiIpCgpmaWcKYGBgCgoKCmBgYHtyfQp0b3BfY2xvc2VubmVzcyA8LSBub2Rlc19kZiAlPiUKICBhcnJhbmdlKGRlc2MoYykpICU+JSBoZWFkKDUpCgpmaWcgPC0gcGxvdF9seSgKICB4ID0gYXMuZmFjdG9yKHRvcF9jbG9zZW5uZXNzJGlkKSwKICB5ID0gdG9wX2Nsb3Nlbm5lc3MkYywKICBuYW1lID0gIlRvcCAxMCBub2RvcyBjb24gbWF5b3IgZ3JhZG8iLAogIHRleHQgPSB0b3BfY2xvc2VubmVzcyRjLCAjIGFkZCB0aGUgdGV4dCBhcmd1bWVudAogIHRleHRwb3NpdGlvbiA9ICJpbnNpZGUiLCAjIGRpc3BsYXkgdGhlIHRleHQgaW5zaWRlIHRoZSBiYXJzCiAgdHlwZSA9ICJiYXIiCikKZmlnIDwtIGZpZyAlPiUKICAgICAgICBsYXlvdXQodGl0bGUgPSAnVG9wIDUgY2VudHJhbGlkYWRlcyBkZSBjZXJjYW7DrWEnLCBwbG90X2JnY29sb3IgPSAiI2U1ZWNmNiIpCgpmaWcKYGBgCgpgYGB7cn0KdG9wX2VpZ2VudmVjdG9yIDwtIG5vZGVzX2RmICU+JQogIGFycmFuZ2UoZGVzYyhlKSkgJT4lIGhlYWQoNSkKCmZpZyA8LSBwbG90X2x5KAogIHggPSBhcy5mYWN0b3IodG9wX2VpZ2VudmVjdG9yJGlkKSwKICB5ID0gdG9wX2VpZ2VudmVjdG9yJGUsCiAgbmFtZSA9ICJUb3AgMTAgbm9kb3MgY29uIG1heW9yIGdyYWRvIiwKICB0ZXh0ID0gdG9wX2VpZ2VudmVjdG9yJGUsICMgYWRkIHRoZSB0ZXh0IGFyZ3VtZW50CiAgdGV4dHBvc2l0aW9uID0gImluc2lkZSIsICMgZGlzcGxheSB0aGUgdGV4dCBpbnNpZGUgdGhlIGJhcnMKICB0eXBlID0gImJhciIKKQpmaWcgPC0gZmlnICU+JQogICAgICAgIGxheW91dCh0aXRsZSA9ICdUb3AgNSBjZW50cmFsaWRhZGVzIGRlIGF1dG92ZWN0b3InLCBwbG90X2JnY29sb3IgPSAiI2U1ZWNmNiIpCgpmaWcKYGBgCgoKYGBge3J9CnRvcF9wYWdlcmFuazwtIG5vZGVzX2RmICU+JQogIGFycmFuZ2UoZGVzYyhwcikpICU+JSBoZWFkKDUpCgpmaWcgPC0gcGxvdF9seSgKICB4ID0gYXMuZmFjdG9yKHRvcF9wYWdlcmFuayRpZCksCiAgeSA9IHRvcF9wYWdlcmFuayRwciwKICBuYW1lID0gIlRvcCAxMCBub2RvcyBjb24gbWF5b3IgZ3JhZG8iLAogIHRleHQgPSB0b3BfcGFnZXJhbmskcHIsICMgYWRkIHRoZSB0ZXh0IGFyZ3VtZW50CiAgdGV4dHBvc2l0aW9uID0gImluc2lkZSIsICMgZGlzcGxheSB0aGUgdGV4dCBpbnNpZGUgdGhlIGJhcnMKICB0eXBlID0gImJhciIKKQpmaWcgPC0gZmlnICU+JQogICAgICAgIGxheW91dCh0aXRsZSA9ICdUb3AgNSBjZW50cmFsaWRhZGVzIGRlIFBhZ2VSYW5rJywgcGxvdF9iZ2NvbG9yID0gIiNlNWVjZjYiKQoKZmlnCmBgYAoKCiMjIyBDb211bmlkYWRlcwoKYGBge3J9CiNDb211bmlkYWRlcyAtIEdpcnZhbi1OZXdtYW4KZWJjIDwtIGVkZ2UuYmV0d2Vlbm5lc3MuY29tbXVuaXR5KGFzLnVuZGlyZWN0ZWQoZykpCmBgYAoKYGBge3J9CnBsb3QoZWJjLCBhcy51bmRpcmVjdGVkKGcpKQpwbG90X2RlbmRyb2dyYW0oZWJjKQpgYGAKCgpgYGB7cn0KI0NvbXVuaWRhZGVzIC0gUGFydGljaW9uZXMgZXNwZWN0cmFsZXMKbGVjIDwtIGxlYWRpbmcuZWlnZW52ZWN0b3IuY29tbXVuaXR5KGFzLnVuZGlyZWN0ZWQoZykpCnBsb3QobGVjLCBhcy51bmRpcmVjdGVkKGcpKQpgYGAKCmBgYHtyfQojQ29tdW5pZGFkZXMgLSBMb3V2YWluCnBsb3QoY2x1c3Rlcl9sb3V2YWluKGFzLnVuZGlyZWN0ZWQoZykpLGFzLnVuZGlyZWN0ZWQoZykpCmBgYAoKYGBge3J9CiNDb211bmlkYWRlcyAtIExlaWRlbgpwbG90KGNsdXN0ZXJfbGVpZGVuKGFzLnVuZGlyZWN0ZWQoZyksb2JqZWN0aXZlX2Z1bmN0aW9uID0gIm1vZHVsYXJpdHkiKSxhcy51bmRpcmVjdGVkKGcpKQpgYGAKCgoKCg==